package neuralnet;

public class NeuralNetwork {

    private NodeLayer[] layers;
    private int size;
    private int numInputs;
    private int numOutputs;
    
    /**
     * Constructs a neural network with the given parameters.
     * 
     * @param numInputs Number of inputs in the input layer.
     * @param numOutputs Number of outputs in the output layer.
     * @param numHidden Number of hidden layers.
     * @param hiddenSize Number of outputs of the hidden layers.
     * @param bias Bias node value.
     * @param learningRate Learning rate.
     * @param momentum Network momentum.
     */
    public NeuralNetwork(int numInputs, int numOutputs, int numHidden, int hiddenSize, 
            double bias, double learningRate, double momentum) {
        
        size = numHidden + 2;
        layers = new NodeLayer[size];
        this.numInputs = numInputs;
        this.numOutputs = numOutputs;
        
        layers[0] = new NodeLayer(null,null,numInputs,bias,learningRate,momentum);
        layers[0].initialize();
        for(int i = 1; i <= numHidden; i++) {
            layers[i] = new NodeLayer(layers[i-1],null,hiddenSize,bias,learningRate,momentum);
            layers[i-1].setOutputLayer(layers[i]);
            layers[i].initialize();
        }
        layers[size-1] = new NodeLayer(layers[size-2],null,numOutputs,bias,learningRate,momentum);
        layers[size-1].initialize();
    }
    
    /**
     * Feeds the given inputs through the entire network and returns the outputs, or
     * null if the inputs are not of the right size.
     * 
     * @param inputs The input array.
     * @return The outputs from the network, or null if the inputs are malformed.
     */
    public double[] feedForward(double[] inputs) {
        
        if(inputs.length != numInputs) return null;
        
        layers[0].setInputs(inputs);
        for(int i = 1; i < size; i++) {
            layers[i].feedForward();
        }
        
        return layers[size-1].getOutputs();
    }
    
    /**
     * Gets the outputs from the network.
     * 
     * @return The outputs from the output layer.
     */
    public double[] getOutputs() {
        return layers[size-1].getOutputs();
    }
    
    /**
     * Trains the network on the given inputs and outputs for the given number of
     * iterations.
     * 
     * @param inputs The input vector.
     * @param desiredOutputs The desired output vector.
     * @param iters The number of training iterations.
     * @return The output after training.ew 
     */
    public double[] train(double[] inputs, double[] desiredOutputs, int iters) {
        
        if(inputs.length != numInputs || desiredOutputs.length != numOutputs) return null;
        
        for(int cnt = 0; cnt < iters; cnt++) {
            feedForward(inputs);
            
            for(int i = size-1; i >= 0; i--) {
                layers[i].calcErrors(desiredOutputs);
                layers[i].adjustWeights();
            }
        }
        
        return getOutputs();
    }
}
